<?php include_once 'config.php'; ?>
<?php include_once 'microakismet.php'; ?>
<?php 
class PPEntry {
  //
  // STATIC members
  //

  // Image/thumbnail processing command
  const CONVERT = '/usr/bin/convert -flatten SRC -background white -thumbnail "SIZE" DST';

  // All fields in the PP_ENTRIES table
  static $fields = array('id', 'has_image', 'auth', 'owner', 'name', 'company', 'email', 'url', 'phone', 'address', 'geocode', 'description');

  /**
   * Get the entry specified by the 'id' query param
   */
  public static function get_current_entry() {
    $id = $_GET['id'] + 0; // Cast to # to prevent sql injections
    if ($id) {
      return PPEntry::get_entry($id);
    }
    return NULL;
  }

  /**
   * Get an entry by id
   */
  public static function get_entry($id) {
    PP::connect();
    $query = "SELECT * FROM ".PP_ENTRIES." WHERE id=${id}";
    $result = PP::query($query);

    $entry = new PPEntry(mysql_fetch_array($result, MYSQL_ASSOC));

    return $entry;
  }

  /**
   * Get all entries
   */
  public static function get_entries($filter, $order) {
    $query = "SELECT * FROM " . PP_ENTRIES .
      ($filter ? " WHERE ${filter}" : "") .
      ($order ? " ORDER BY ${order}" : "") .
      (" LIMIT 500");
    $result = PP::query($query);

    $entries = array();
    while ($data = mysql_fetch_array($result, MYSQL_ASSOC)) {
      $entries[] = new PPEntry($data);
    }

    return $entries;
  }

  public static function htmlify($s, $escape) {
    $s = htmlspecialchars($s);
    return $escape ? preg_replace('/\n/', '<br/>', $s) : $s;
  }

  //
  // INSTANCE members
  //

  var $data;
  var $editable = false;

  function PPEntry($data) {
    $this->data = $data ? $data : array();
  }

  /** 
   * Supply an authcode to allow this entry to be edited
   */
  public function auth($authcode) {
    $this->editable = $this->data['auth'] == NULL ||
      $this->data['auth'] == $authcode ||
      PP_MASTER_PWD == $authcode;
    return $this->isEditable();
  }

  /**
   * "Is this a new entry? (i.e. hasn't been saved to the DB yet)"
   */
  public function isNew() {
    return !$this->data['id'];
  }

  /**
   * "Is this a new entry? (i.e. hasn't been saved to the DB yet)"
   */
  public function isSecure() {
    return !!$this->data['auth'];
  }

  /**
   * "Is user authorized to edit this entry?"
   */
  public function isEditable() {
    return $this->editable;
  }

  /**
   * Validate the contents of this entry
   */
  public function validate() {
    $d = $this->data;
    return pp_validate_email($d['owner']) && ($d['name'] || $d['company']);
  }

  /**
   * Reset the password
   */
  public function reset_password() {
    // Choose a new password
    $newpwd = substr(md5(rand()), 5, 8);
    $newauth = md5($newpwd);

    // Update it in the database
    $id = $this->data['id'];
    if ($id) {
      $query = "UPDATE ".PP_ENTRIES." SET auth='${newauth}' WHERE id=${id}"; 
      PP::query($query);

      // Send out a notification email
      $h = $this->htmlFields();
      $title = $h['company_name'];
      $to = $this->data['owner'];
      $subject = "BendTech.com entry password has been reset";
      $detail_url = $this->getUrl('edit');
      $body = "The password for your BendTech Directory Entry, \"${title}\", has been reset to:\n\n    \"${newpwd}\".\n\nYou can edit this entry by going to the following URL:\n\n    ${detail_url}";
      mail($to, $subject, $body, "From: no-reply@bendtech.com");
    } else {
      die('Entry does not have a valid ID');
    }
  }

  /**
   * Get an sql-escaped entry value
   */
  public function getSql($val) {
    $v = $this->data[$val];
    return $v ? "'".mysql_real_escape_string($v)."'" : 'NULL';
  }

  /**
   * Get an html-escaped entry value
   */
  public function getHtml($key) {
    $v = $this->data[$key];
    return $v ? htmlspecialchars($v) : '';
  }

  /**
   * Get map of displayable html for the entry fields
   */
  public function htmlFields() {
    $fields = array_merge(array(), PPEntry::$fields);
    unset($fields['id']);
    unset($fields['owner']);
    unset($fields['created']);
    unset($fields['modified']);

    // Get html-ified versions of each field
    $h = array();
    foreach($fields as $field) {
      $h[$field] = PPEntry::htmlify($this->data[$field], true);
    }

    // Create summary field
    $s = $this->data['description'];
    $s = preg_replace('/\..*/', '', $s);
    $max = 80;
    if (strlen($s) >= $max) {
      $s = substr($s, 0, $max-3).'...';
    }
    $h['summary'] = $s;

    // Create name,company and company,name values
    $nc = array();
    if ($h['name']) $nc[] = $h['name'];
    if ($h['company']) $nc[] = $h['company'];
    $h['name_company'] = count($nc) ? implode($nc, ', ') : 'Anonymous';
    $nc = array_reverse($nc);
    $h['company_name'] = count($nc) ? implode($nc, ', ') : '<em>(anonymous)</em>';

    return $h;
  }

  /**
   * Return URL to designated resource
   */
  public function getUrl($type) {
    $id = $this->data['id'];
    switch($type) {
      case 'view':
        return PP_ROOT."/detail.php?id=${id}";
      case 'edit':
        return PP_ROOT."/edit.php?id=${id}";
      case 'password_reset':
        return PP_ROOT."/reset.php?id=${id}";
      case 'image':
        return PP_ROOT . "/" . $this->getPath('image');
      case 'thumb':
        return PP_ROOT . "/" . $this->getPath('thumb');
    }
    return '';
  }

  public function getLink($type, $text) {
    return '<a href="' . $this->getUrl($type) . '">' . $text . '</a>';
  }

  public function getImage($type) {
    $hi = $this->data['has_image'];
    return $hi ? '<img src="' . $this->getUrl($type) . '" />' : '';
  }

  public function getPath($type) {
    $id = $this->data['id'];
    switch($type) {
      case 'image':
        return PP_UPLOAD_DIR."/${id}.jpg";
      case 'thumb':
        return PP_UPLOAD_DIR."/${id}_thumb.jpg";
    }
    return '';
  }

  /**
   * Returns "lon,lon,alt" or NULL
   */
  public function getGeocode() {
    return $this->data['geocode'];
  }

  /**
   * Merge values from the provided object
   */
  public function merge($o) {
    $fields = array_merge(array(), PPEntry::$fields);
    unset($fields['id']);
    foreach($fields as $field) {
      $this->data[$field] = trim($o[$field]);
    }
  }

  /**
   * Change the password (but don't save to db)
   */
  public function setPassword($passwd) {
    $this->data['auth'] = $passwd ? md5($passwd) : NULL;
  }

  /**
   * Specify an image to associate with the entry
   */
  public function setImage($src) {
    if (!$this->isEditable()) {
      die('setImage(): Entry is not editable');
    }

    $id = $this->data['id'];

    if (!$id) {
      die('setImage(): Entry does not have id.');
    }

    if ($src) {
      // Create upload dir if necessary
      if (!file_exists(PP_UPLOAD_DIR)) {
        mkdir(PP_UPLOAD_DIR);
      }

      // Create 192px version
      $cmd = str_replace(array('SRC', 'DST', 'SIZE'),
        array($src, $this->getPath('image'), '192x192>'), PPEntry::CONVERT);
      exec($cmd);

      // Create 32px versior/bin/convert';
      $cmd = str_replace(array('SRC', 'DST', 'SIZE'),
        array($src, $this->getPath('thumb'), '96x32'), PPEntry::CONVERT);
      exec($cmd);
    } else {
      // Remove images
      foreach (array($this->getPath('image'), $this->getPath('thumb')) as $path) {
        !file_exists($path) or unlink($path);
      }
    }

    // Update database
    $query = "UPDATE ".PP_ENTRIES." SET has_image=".($src ? 1 : 0)." WHERE id=${id}"; 
    PP::query($query);
  }

  /**
   * Save this entry to the db
   */
  public function save() {
    if (!$this->isEditable()) {
      die('save(): Entry is not editable');
    }

    // Fetch geocode
    $this->fetchGeocode();

    // Connect to db
    PP::connect();

    // Get map of fields we want to save
    $fields = array_merge(array(), $this->data);
    unset($fields['id']);
    unset($fields['modified']);
    unset($fields['has_image']);

    // Get sql-safe version of all values
    foreach ($fields as $key => $value) {
      $fields[$key] = $this->getSql($key);
    }

    // Insert or update?
    $isInsert = $this->data['id'] == NULL;
    $constants = get_defined_constants();
    $spam = false;

    if ($isInsert) {
      // Set creation timestamp
      $fields['created'] = 'NOW()';

      $values = implode(array_values($fields), ',');
      $fields = implode(array_keys($fields), ',');
      $query = "INSERT ".PP_ENTRIES." (${fields}) VALUES (${values})";
    } else {
      $id = $this->data['id'];
      $pairs = array();
      foreach ($fields as $key => $value) {
        $pairs[] = "${key}=${value}";
      }
      $pairs = implode($pairs, ',');
      $query = "UPDATE ".PP_ENTRIES." SET ${pairs} WHERE id=${id}"; 
    }
    // Execute the query
    PP::query($query);

    // Get the assigned id for newly inserted records
    if ($isInsert) {
      $this->data['id'] = mysql_insert_id();
    }
  }

  /**
   * Delete this entry from the db
   */
  public function delete() {
    if (!$this->isEditable()) {
      die('delete(): Entry is not editable');
    }
    PP::connect();


    // Clean up image(s)
    $this->setImage(NULL);

    // Delete from db
    $id = $this->data['id'];
    $query = "DELETE FROM ".PP_ENTRIES." WHERE id=${id}"; 
    PP::query($query);
  }

  /**
   * Fetch the geocode for this entry
   * Code (mostly) taken from http://www.developer.com/db/article.php/3621981
   */
  function fetchGeocode() {
    $address = $this->data['address'];
    $key = PP_GEOCODE_KEY;
    if (!$address || !$key) {
      return false;
    }

    $address = urlencode($address);
    $url = "http://maps.google.com/maps/geo?q=${address}&output=xml&key=${key}";

    // Fetch url. We'd like to do:
    //    $response = file_get_contents($url);
    // ...but has this disabled in PHP.  Instead we use curl, ala
    // http://wiki.dreamhost.com/index.php/CURL
    $ch = curl_init();
    $timeout = 5; // set to zero for no timeout
    curl_setopt ($ch, CURLOPT_URL, $url);
    curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
    $response = curl_exec($ch);
    $start = microtime(true);
    curl_close($ch);
    $stop = microtime(true);
    // Parse the returned XML file
    try {
      $xml = new SimpleXMLElement($response);
      if ($xml->Response->Status->code == 200) {
        // Normal response
        $this->data['geocode'] = (string)$xml->Response->Placemark->Point->coordinates;
      } else {
        // For other codes, see
        // http://www.google.com/apis/maps/documentation/reference.html#GGeoStatusCode
      }
    } catch (Exception $e) {
      // ??? - Not much we can do here.  Fail silently since this isn't a critical operation
    }
  }
}
